﻿#light

namespace CodeBlock.AMD64

open System
open System.Linq

open Mono.Cecil
open Mono.Cecil.Cil

open Microsoft.FSharp.Collections

open CodeBlock
open CodeBlock.AMD64.Assembly
open CodeBlock.AMD64.TypeResolver

open RuntimeUtils

type CompilationState =
        {   Stack: StackItem list;
            Code: InstructionAMD64 list; }

type LogEntry<'a, 'b> =
    {   Entry: 'a;
        Position: 'b;   }

type Warning(message)=
    inherit Exception(message)

type Compiler(resolver: BaseAssemblyResolver, output: Action<LogEntry<Exception, Instruction * StackItem list>>) =
    let nullOutput (error: LogEntry<Exception, Instruction * StackItem seq>) =
        raise error.Entry
    
    let output subj =
        output.Invoke(subj)
    
    let layout = MemoryLayoutAMD64(resolver)
    
    [<OverloadID("func")>]
    member this.FullName (func: MethodDefinition)=
        "/" + func.DeclaringType.FullName + "/" + func.Name + "/"
    
    member this.Compile (func: MethodDefinition) =
        let invalid_program() = failwith "invalid program"
        // let body = func.GetMethodBody()
        if not func.IsIL then raise(NotImplementedException("Non-cil funcitons"))
        if func.Body = null then raise(NotImplementedException("functions without body"))
        let il = func.Body.Instructions.OfType<Instruction>() |> List.of_seq
        let args = func.Parameters.OfType<ParameterDefinition>() |> List.of_seq
        let vars = func.Body.Variables.OfType<VariableDefinition>() |> List.of_seq
        let assembly = func.DeclaringType.Module.Assembly
        
//        let functions =
//            let list = System.Collections.Generic.List<int * MethodInfo>()
//            
//            for t in assembly.GetTypes() do
//                for func in t.GetMethods() do
//                    if func.DeclaringType.Assembly.Equals(assembly) then list.Add(func.MetadataToken, func)
//            
//            Map(list)
        
        let name = this.FullName func
        // let id = "/functions/" + assembly.Name.FullName + "/" + func.MetadataToken.ToString()
        
        let args_size = args |> List.sum_by (fun value -> layout.[resolver.ResolveType(value.ParameterType)].Size |> size_align 8u)
        let locals_size = vars |> List.sum_by (fun var -> layout.[resolver.ResolveType(var.VariableType)].Size |> size_align 8u)
        // let locals_size = List.sum_by(fun (value: LocalVariableInfo) -> type_size value.LocalType |> size_align 8) vars
        
        let args_start =
            // arguments
            // old IP
            // old RBP  <- RBP
            8u + args_size
        
        let PROLOGUE code=
            code
            |> ~/ name
            |> PUSH RBP
            |> MOV  RBP RSP
            |> SUB  RSP ~*locals_size
        
        
        let EPILOGUE code =
//            let code = 
//                match func.ReturnType.FullName with
//                | "System.Void" -> code
//                | "System.UInt64" | "System.Int64" | "System.Int32" | "System.UInt32" | "System.Int16" | "System.UInt16"
//                | "System.Byte" | "System.SByte" | "System.IntPtr" | "System.UIntPtr" ->
//                    code
//                    |> POP  RAX
//                | _ ->
//                    not_implemented "epilogue return value"
            
            code
            |> MOV  RSP RBP
            |> POP  RBP
            |> RET  ~*args_size
        
        let emit_ldstack offset size code =
            let size = int ((size_align 8u size) / 8u)
            let rec LDSTACK index code =
                let offset = offset + (size - index) * 8 - 8
                
                if index < size - 1 then
                    code
                    |> PUSH (RBP + offset)
                    |> LDSTACK (index + 1)
                else
                    code
                    |> PUSH (RBP + offset)
            
            LDSTACK 0 code
        
        let emit_ststack offset size code =
            let size = int ((size_align 8u size) / 8u)
            let rec STSTACK index code =
                let offset = offset + index * 8
                
                if index < size - 1 then
                    code
                    |> POP  (RBP + offset)
                    |> STSTACK  (index + 1)
                else
                    code
                    |> POP  (RBP + offset)
            
            STSTACK 0 code
        
        let arg_offsets =
            let rec arg_offsets arg prev_offset list =
                if arg = 0 then
                    prev_offset :: list
                else
                    let arg_type = resolver.ResolveType args.[arg].ParameterType
                    let arg_size =
                        layout.[arg_type].Size
                        |> size_align 8u
                    let offset = prev_offset + int arg_size
                    arg_offsets (arg - 1) offset (prev_offset :: list)
            
            let result =
                if args.Length = 0 then
                    []
                else
                    arg_offsets (args.Length - 1) (if func.IsStatic then 16 else 24) []
                    |> List.rev
            
            List.to_array result
        
        let local_offsets =
            let rec local_offsets var prev_offset list =
                if var = vars.Length then
                    list
                else
                    let var_type = resolver.ResolveType(vars.[var].VariableType)
                    let var_size =
                        layout.[var_type].Size
                        |> size_align 8u
                    let offset = prev_offset - int var_size
                    local_offsets (var + 1) offset (offset :: list)
            
            let result =
                local_offsets 0 0 []
                |> List.rev
            
    //        if result.Length >= 2 then
    //            let result = List.to_array result
    //            let loc0 = result.[0]
    //            let loc1 = result.[1]
    //            Debug.Assert(Math.Abs(result.[0]) < Math.Abs(result.[1]))
            
            List.to_array result
        
        let rec compile state (instruction: Instruction) =
            let local_name = name + instruction.Offset.ToString() + "/"
            let local name = ~&(local_name + name)
            let (~//) name = LABEL (local name)
            let code =
                state.Code
                |> ~//""
                |> COMMENT (instruction.OpCode.Code.ToString())
            
            let stack = state.Stack
            
            let out error =
                output  {   Entry = error;
                            Position = instruction, stack;  }
            
            match instruction.OpCode.Code, state.Stack with
            // nop
            | Code.Nop, stack ->
                let code =
                    code
                    |> NOP
                {   state with
                        Code = code;    }
            
            // binary numeric
            // add
            | Code.Add, I32 :: I32 :: stack ->
                let code = 
                    code
                    |> POP  RAX
                    |> POP  RBX
                    |> ADD  EAX EBX
                    |> PUSH RAX
                { state with
                    Code = code;
                    Stack = I32 :: stack;   }
            
            | Code.Add, T64 :: B64 :: stack when ((T64 = I64) || (T64 = Native)) && (T64 = B64) ->
                let code =
                    code
                    |> POP  RAX
                    |> POP  RBX
                    |> ADD  RAX RBX
                    |> PUSH RAX
                { state with
                    Code = code;
                    Stack = T64 :: stack;   }
            
            | Code.Add, I32 :: V64 :: stack when (V64 = Native) || (V64 = Reference) ->
                let code =
                    code
                    |> POP      RAX // I32
                    |> POP      RBX
                    |> MOVSXD   RAX EAX
                    |> ADD      RAX RBX
                    |> PUSH     RAX
                { state with
                    Code = code;
                    Stack = V64 :: stack;   }
                
            | Code.Add, V64 :: I32 :: stack when (V64 = Native) || (V64 = Reference) ->
                let code =
                    code
                    |> POP      RBX // I32
                    |> POP      RAX
                    |> MOVSXD   RAX EAX
                    |> ADD      RAX RBX 
                    |> PUSH     RAX
                { state with
                    Code = code;
                    Stack = V64 :: stack;   }
                
            | Code.Add, Float :: Float :: rest ->
                output  {   Entry = not_implemented "float + float";
                            Position = instruction, stack   }
                { state with
                    Code = code;
                    Stack = Float :: rest; }
            
            | Code.Add, _ ->
                invalid_code "invalid add operands" |> out
                
                invalid_program()
            //add
             
            //sub
            | Code.Sub, I32 :: I32 :: stack ->
                let code =
                    code
                    |> POP  RBX
                    |> POP  RAX
                    |> SUB  EAX EBX
                    |> PUSH RAX
                { state with
                    Code = code;
                    Stack = I32 :: stack;   }
                
            | Code.Sub, T64 :: B64 :: stack when ((T64 = I64) || (T64 = Native)) && (T64 = B64) ->
                let code =
                    code
                    |> POP  RBX
                    |> POP  RAX
                    |> SUB  RAX RBX
                    |> PUSH RAX
                {   Stack = T64 :: stack;
                    Code = code;    }
                
            | Code.Sub, T64 :: Reference :: stack when (T64 = Reference) || (T64 = Native) ->
                let code =
                    code
                    |> POP  RBX
                    |> POP  RAX
                    |> SUB  RAX RBX
                    |> PUSH RAX
                { state with
                    Code = code;
                    Stack = Reference :: stack; }
                
            | Code.Sub, I32 :: Native :: stack ->
                let code =
                    code
                    |> POP  RBX //i32
                    |> POP  RAX
                    |> MOVSXD RBX EBX
                    |> SUB  RAX RBX
                    |> PUSH RAX
                { state with
                    Code = code;
                    Stack = Native :: stack;    }
            
            | Code.Sub, I32 :: Reference :: stack ->
                let code = 
                    code
                    |> POP  RBX //i32
                    |> POP  RAX
                    |> MOVSXD RBX EBX
                    |> SUB  RAX RBX
                    |> PUSH RAX
                { state with
                    Code = code;
                    Stack = Reference :: stack; }
                
            | Code.Sub, Native :: I32 :: stack ->
                let code =
                    code 
                    |> POP  RBX
                    |> POP  RAX //i32
                    |> MOVSXD RAX EAX
                    |> SUB  RAX RBX
                    |> PUSH RAX
                { state with
                    Code = code;
                    Stack = Native :: stack;    }
                
            | Code.Sub, Float :: Float :: rest ->
                not_implemented "float - float" |> out
                
                { state with
                    Code = code;
                    Stack = Float :: stack; }
            
            | Code.Sub, _ ->
                invalid_code "invalid sub operands" |> out
                
                invalid_program()
            //sub
             
            //and, or, xor
            | OP, I32 :: I32 :: stack when (OP = Code.Or) || (OP = Code.Xor) || (OP = Code.And) ->
                let op =
                    match OP with
                    | Code.And -> Operation.And
                    | Code.Or -> Operation.Or
                    | Code.Xor -> Operation.Xor
                    | _ -> failwith "invalid code in binary handling"
                
                let code =
                    code
                    |> POP  RAX
                    |> POP  RBX
                    |> BINARY op EAX EBX
                    |> PUSH RAX
                { state with
                    Code = code;
                    Stack = I32 :: stack;   }
                
            | OP, I32 :: Native :: stack when (OP = Code.Or) || (OP = Code.Xor) || (OP = Code.And) ->
                let op =
                    match OP with
                    | Code.And -> Operation.And
                    | Code.Or -> Operation.Or
                    | Code.Xor -> Operation.Xor
                    | _ -> failwith "invalid code in binary handling"
                
                let code =
                    code
                    |> POP  RAX //i32
                    |> POP  RBX
                    |> MOVSXD RAX EAX
                    |> BINARY op RAX RBX
                    |> PUSH RAX
                { state with
                    Code = code;
                    Stack = Native :: stack;    }
                
            | OP, Native :: I32 :: stack when (OP = Code.Or) || (OP = Code.Xor) || (OP = Code.And) ->
                let op =
                    match OP with
                    | Code.And -> Operation.And
                    | Code.Or -> Operation.Or
                    | Code.Xor -> Operation.Xor
                    | _ -> failwith "invalid code in binary handling"
                
                let code =
                    code 
                    |> POP  RBX
                    |> POP  RAX //i32
                    |> MOVSXD RAX EAX
                    |> BINARY op RAX RBX
                    |> PUSH RAX
                
                { state with
                    Code = code;
                    Stack =  Native :: stack;   }
                
            | OP, T64 :: B64 :: stack when (((T64 = I64) || (T64 = Native)) && (T64 = B64)) && ((OP = Code.Or) || (OP = Code.Xor) || (OP = Code.And)) ->
                let op =
                    match OP with
                    | Code.And -> Operation.And
                    | Code.Or -> Operation.Or
                    | Code.Xor -> Operation.Xor
                    | _ -> failwith "invalid code in binary handling"
                
                let code =
                    code
                    |> POP  RBX
                    |> POP  RAX
                    |> BINARY op RAX RBX
                    |> PUSH RAX
                
                { state with
                    Code = code;
                    Stack = Native :: stack;    }
                
            | OP, _ when (OP = Code.Or) || (OP = Code.Xor) || (OP = Code.And) ->
                let op =
                    match OP with
                    | Code.And -> Operation.And
                    | Code.Or -> Operation.Or
                    | Code.Xor -> Operation.Xor
                    | _ -> failwith "invalid code in binary handling"
                
                let error = "invalid " + op.ToString() + " operands"
                invalid_code error |> out
                
                invalid_program()
             //and, or, xor
             
             //div, rem
            | OP, I32 :: I32 :: stack when (OP = Code.Div) || (OP = Code.Rem) ->
                let op =
                    match OP with
                    | Code.Rem -> RDX
                    | Code.Div -> RAX
                    | _ -> failwith "invalid code in binary handling"
                
                let code =
                    code
                    |> POP  RBX
                    |> POP  RAX
                    |> CDQ
                    |> IDIV EBX
                    |> PUSH op
                
                not_implemented "div: check for ArithmeticException and DivideByZeroException" |> out
                
                { state with
                    Code = code;
                    Stack = I32 :: stack;   }
                
            | OP, T64 :: B64 :: stack when (((T64 = I64) || (T64 = Native)) && (T64 = B64)) && ((OP = Code.Div) || (OP = Code.Rem)) ->
                let op =
                    match OP with
                    | Code.Rem -> RDX
                    | Code.Div -> RAX
                    | _ -> failwith "invalid code in binary handling"
                
                let code = 
                    code
                    |> POP  RBX
                    |> POP  RAX
                    |> CQO
                    |> IDIV RBX
                    |> PUSH op
                
                not_implemented "div: check for ArithmeticException and DivideByZeroException" |> out
                
                { state with
                    Code = code;
                    Stack = I64 :: stack;   }
            
            | OP, I32 :: Native :: stack when (OP = Code.Div) || (OP = Code.Rem) ->
                let op =
                    match OP with
                    | Code.Rem -> RDX
                    | Code.Div -> RAX
                    | _ -> failwith "invalid code in binary handling"
                
                let code = 
                    code
                    |> POP  RBX //i32
                    |> POP  RAX
                    |> CQO
                    |> MOVSXD   RBX EBX
                    |> IDIV RBX
                    |> PUSH op
                
                not_implemented "div: check for ArithmeticException and DivideByZeroException" |> out
                
                { state with
                    Code = code;
                    Stack = Native :: stack;    }
                
            | OP, Native :: I32 :: stack when (OP = Code.Div) || (OP = Code.Rem) ->
                let op =
                    match OP with
                    | Code.Rem -> RDX
                    | Code.Div -> RAX
                    | _ -> failwith "invalid code in binary handling"
                
                let code = 
                    code
                    |> POP  RBX
                    |> POP  RAX
                    |> MOVSXD RAX EAX
                    |> CQO
                    |> IDIV RBX
                    |> PUSH op
                
                not_implemented "div: check for ArithmeticException and DivideByZeroException" |> out
                
                { state with
                    Code = code;
                    Stack = Native :: stack;    }
                
            | Code.Div, Float :: Float :: stack | Code.Rem, Float :: Float :: stack ->
                not_implemented "float / float" |> out
                
                { state with
                    Code = code;
                    Stack = Float :: stack; }
                
            | Code.Div, _ | Code.Rem, _ ->
                invalid_code "invalid div/rem operands" |> out
                
                invalid_program()
            //div, rem
             
            //div.un, rem.un
            | OP, I32 :: I32 :: stack when (OP = Code.Div_Un) || (OP = Code.Rem_Un) ->
                let op =
                    match OP with
                    | Code.Rem_Un -> RDX
                    | Code.Div_Un -> RAX
                    | _ -> failwith "invalid code in binary handling"
                
                let code = 
                    code
                    |> POP  RBX
                    |> POP  RAX
                    |> XOR  EDX EDX
                    |> DIV  EBX
                    |> PUSH op
                
                not_implemented "div.un: check for DivideByZeroException" |> out
                
                { state with
                    Code = code;
                    Stack = I32 :: stack;   }
                
            | OP, T64 :: B64 :: stack when (((T64 = I64) || (T64 = Native)) && (T64 = B64)) && ( (OP = Code.Div_Un) || (OP = Code.Rem_Un)) ->
                let op =
                    match OP with
                    | Code.Rem_Un -> RDX
                    | Code.Div_Un -> RAX
                    | _ -> failwith "invalid code in binary handling"
                
                let code = 
                    code
                    |> POP  RBX
                    |> POP  RAX
                    |> XOR  EDX EDX
                    |> DIV  RBX
                    |> PUSH op
                
                not_implemented "div.un: check for DivideByZeroException" |> out
                
                { state with
                    Code = code;
                    Stack = I64 :: stack;   }
                
            | OP, I32 :: Native :: stack when  (OP = Code.Div_Un) || (OP = Code.Rem_Un) ->
                let op =
                    match OP with
                    | Code.Rem_Un -> RDX
                    | Code.Div_Un -> RAX
                    | _ -> failwith "invalid code in binary handling"
                
                let code = 
                    code
                    |> POP  RBX //i32
                    |> POP  RAX
                    |> XOR  EDX EDX
                    |> MOV  EBX EBX //nulls hi 32 bits of RBX
                    |> DIV  RBX
                    |> PUSH op
                    
                not_implemented "div: check for DivideByZeroException" |> out
                
                { state with
                    Code = code;
                    Stack = Native :: stack;    }
                
            | OP, Native :: I32 :: stack when (OP = Code.Div_Un) || (OP = Code.Rem_Un) ->
                let op =
                    match OP with
                    | Code.Rem_Un -> RDX
                    | Code.Div_Un -> RAX
                    | _ -> raise(ExecutionEngineException("invalid code in binary handling"))
                
                let code = 
                    code
                    |> POP  RBX
                    |> POP  RAX //i32
                    |> MOV  EAX EAX
                    |> XOR  EDX EDX
                    |> DIV  RBX
                    |> PUSH op
                
                not_implemented "div: check for DivideByZeroException" |> out
                
                { state with
                    Code = code;
                    Stack = Native :: stack;    }
                
            | Code.Div_Un, _ | Code.Rem_Un, _ ->
                invalid_code "invalid div.un/rem.un operands" |> out
                
                invalid_program()
            //div.un, rem.un
             
            //mul
            | Code.Mul, I32 :: I32 :: stack ->
                let code =
                    code
                    |> POP  RAX
                    |> POP  RBX
                    |> IMUL EBX
                    |> PUSH RAX
                
                { state with
                    Code = code;
                    Stack = I32 :: stack;   }
                
            | Code.Mul, I32 :: Native :: stack ->
                let code =
                    code
                    |> POP  RAX
                    |> POP  RBX
                    |> MOVSXD RAX EAX
                    |> IMUL RBX
                    |> PUSH RAX
                
                { state with
                    Code = code;
                    Stack = Native :: stack;    }
                
            | Code.Mul, Native :: I32 :: stack ->
                let code =
                    code
                    |> POP  RBX
                    |> POP  RAX //i32
                    |> MOVSXD RAX EAX
                    |> IMUL RBX
                    |> PUSH RAX
                
                { state with
                    Code = code;
                    Stack = Native :: stack }
                
            | Code.Mul, T64 :: B64 :: stack when ((T64 = I64) || (T64 = Native)) && (T64 = B64) ->
                let code =
                    code
                    |> POP  RAX
                    |> POP  RBX
                    |> MOVSXD RAX EAX
                    |> IMUL RBX
                    |> PUSH RAX
                
                { state with
                    Code = code;
                    Stack = I64 :: stack;   }
                
            | Code.Mul, Float :: Float :: stack ->
                not_implemented "float * float" |> out
                
                { state with
                    Code = code;
                    Stack = Float :: stack; }
                
            | Code.Mul, _ ->
                invalid_code "invalid mul operands" |> out
                
                invalid_program()
            //mul
             
            //stack ops
            //dup
            | Code.Dup, value :: stack ->
                let code = 
                    code
                    |> POP  RAX
                    |> PUSH RAX
                    |> PUSH RAX
                
                if value = StackItem.Object then not_implemented "struct duplication" |> out
                
                { state with
                    Code = code;
                    Stack = value :: value :: stack;    }
            //dup
             
            //pop
            | Code.Pop, value :: stack ->
                let code =
                    code
                    |> POP  RAX
                
                if value = StackItem.Object then not_implemented "struct popping" |> out
                
                { state with
                    Code = code;
                    Stack = stack;  }
            //pop
             
            //ldc*
            | Code.Ldc_I4, stack ->
                let value = (unbox<int> instruction.Operand) |> int64
                let code =
                    code
                    |> PUSH ~*value
                
                { state with
                    Code = code;
                    Stack = I32 :: stack;   }
                
            | Code.Ldc_I8, stack ->
                let value = unbox<int64> instruction.Operand
                let code =
                    code
                    |> MOV  RAX ~*value
                    |> PUSH RAX
                
                { state with
                    Code = code;
                    Stack = I64 :: stack;   }
                
            | Code.Ldc_R4, stack ->
                not_implemented("ldc.r4") |> out
                
                { state with
                    Code = code;
                    Stack = Float :: stack; }
                
            | Code.Ldc_R8, stack ->
                not_implemented("ldc.r8") |> out
                
                { state with
                    Code = code;
                    Stack = Float :: stack; }
                
            | ldc, stack  when (int ldc >= 21) && (int ldc <= 30)->
                let ldc = int ldc - 22
                let code =
                    code
                    |> PUSH ~*ldc
                
                { state with
                    Code = code;
                    Stack = I32 :: stack;   }
                
            | Code.Ldc_I4_S, stack ->
                let value = unbox<SByte> instruction.Operand |> int
                let code =
                    code
                    |> PUSH ~*value
                
                { state with
                    Code = code;
                    Stack = I32 :: stack;   }
            //ldc*
            
            //ldstr
            | Code.Ldstr, stack ->
                not_implemented "loading string constants" |> out
                { state with
                    Code = code;
                    Stack = Object :: stack;    }
            //ldstr
            
            
            //ldarga
            | Code.Ldarga_S, stack ->
                let param = instruction.Operand :?> ParameterDefinition
                let arg = args.TakeWhile(fun arg -> arg <> param).Count()
                let arga = if func.IsStatic then arg_offsets.[arg] elif arg <> 0 then arg_offsets.[arg - 1] else 16
                let code=
                    code
                    |> LEA  RAX (RBP + arga)
                    |> PUSH RAX
                
                { state with
                    Code = code;
                    Stack = Reference :: stack; }
                
            | Code.Ldarg, stack ->
                let arg = unbox<uint16> instruction.Operand |> int
                let arga = if func.IsStatic then arg_offsets.[arg] else 16
                let code=
                    code
                    |> LEA  RAX (RBP + arga)
                    |> PUSH RAX
                
                { state with
                    Code = code;
                    Stack = Reference :: stack; }
            //ldarga
            
            //ldloca
            | Code.Ldloc_S, stack ->
                let loc = unbox<byte> instruction.Operand |> int
                let loca = local_offsets.[loc]
                let code=
                    code
                    |> LEA  RAX (RBP + loca)
                    |> PUSH RAX
                
                { state with
                    Code = code;
                    Stack = Reference :: stack; }
                
            | Code.Ldloc, stack ->
                let loc = unbox<uint16> instruction.Operand |> int
                let loca = local_offsets.[loc]
                let code=
                    code
                    |> LEA  RAX (RBP + loca)
                    |> PUSH RAX
                
                { state with
                    Code = code;
                    Stack = Reference :: stack; }
            //ldloca
            
            //ldarg
            | arg, stack when (arg >= Code.Ldarg_0) && (arg <= Code.Ldarg_3) ->
                let arg = int(arg - Code.Ldarg_0)
                let arga = if func.IsStatic then arg_offsets.[arg] elif arg <> 0 then arg_offsets.[arg - 1] else 16
                let arg_type = 
                    (if func.IsStatic then args.[arg].ParameterType elif arg <> 0 then args.[arg - 1].ParameterType else func.DeclaringType)
                    |> resolver.ResolveType
                //let arg_size = 
                let code =
                    code
                    |> emit_ldstack arga (layout.[arg_type].Size)
                
                { state with
                    Code = code;
                    Stack = layout.GetStackType arg_type :: stack;   }
                
            | Code.Ldarg_S, stack ->
                let arg = unbox<byte> instruction.Operand |> int
                let arga = if func.IsStatic then arg_offsets.[arg] elif arg <> 0 then arg_offsets.[arg - 1] else 16
                let arg_type = (if func.IsStatic then args.[arg].ParameterType elif arg <> 0 then args.[arg - 1].ParameterType else func.DeclaringType) |> resolver.ResolveType
                let code =
                    code
                    |> emit_ldstack arga (layout.[arg_type].Size)
                
                { state with
                    Code = code;
                    Stack = layout.GetStackType (arg_type) :: stack; }
                
            | Code.Ldarg, stack ->
                let arg = unbox<uint16> instruction.Operand |> int
                let arga = if func.IsStatic then arg_offsets.[arg] elif arg <> 0 then arg_offsets.[arg - 1] else 16
                let arg_type =
                    (if func.IsStatic then args.[arg].ParameterType elif arg <> 0 then args.[arg - 1].ParameterType else func.DeclaringType)
                    |> resolver.ResolveType
                    
                let code =
                    code
                    |> emit_ldstack arga (layout.[arg_type].Size)
                
                { state with
                    Code = code;
                    Stack = layout.GetStackType (arg_type) :: stack; }
            //ldarg
            
            //ldloc
            | loc, stack when (loc >= Code.Ldloc_0) && (loc <= Code.Ldloc_3) ->
                let loc = int(loc - Code.Ldloc_0)
                let offset = local_offsets.[loc]
                let loc_type =
                    vars.[loc].VariableType
                    |> resolver.ResolveType
                let code =
                    code
                    |> emit_ldstack offset (layout.[loc_type].Size)
                
                { state with
                    Code = code;
                    Stack = layout.GetStackType (loc_type) :: stack; }
                
            | Code.Ldloc_S, stack ->
                let loc = unbox<byte> instruction.Operand |> int
                let offset = local_offsets.[loc]
                let loc_type =
                    vars.[loc].VariableType
                    |> resolver.ResolveType
                let code =
                    code
                    |> emit_ldstack offset (layout.[loc_type].Size)
                
                { state with
                    Code = code;
                    Stack = layout.GetStackType (loc_type) :: stack; }
            
            | Code.Ldloc, stack ->
                let loc = unbox<uint16> instruction.Operand |> int
                let offset = local_offsets.[loc]
                let loc_type =
                    vars.[loc].VariableType
                    |> resolver.ResolveType
                let code =
                    code
                    |> emit_ldstack offset (layout.[loc_type].Size)
                
                { state with
                    Code = code;
                    Stack = layout.GetStackType (loc_type) :: stack; }
            //ldloc
            
            //stloc
            | loc, itemType :: stack when (loc >= Code.Stloc_0) && (loc <= Code.Stloc_3) ->
                let loc = int(loc - Code.Stloc_0)
                let offset = local_offsets.[loc]
                let loc_type =
                    vars.[loc].VariableType
                    |> resolver.ResolveType
                let code =
                    code
                    |> emit_ststack offset (layout.[loc_type].Size)
                
                { state with
                    Code = code;
                    Stack = stack;  }
            
            | Code.Stloc_S, item :: stack ->
                let loc = unbox<byte> instruction.Operand |> int
                let offset = local_offsets.[loc]
                let loc_type =
                    vars.[loc].VariableType
                    |> resolver.ResolveType
                let code =
                    code
                    |> emit_ststack offset (layout.[loc_type].Size)
                
                { state with
                    Code = code;
                    Stack = stack;  }
            
            | Code.Stloc, stack ->
                let loc = unbox<uint16> instruction.Operand |> int
                let offset = local_offsets.[loc]
                let loc_type =
                    vars.[loc].VariableType
                    |> resolver.ResolveType
                let code =
                    code
                    |> emit_ststack offset (layout.[loc_type].Size)
                
                { state with
                    Code = code;
                    Stack = stack;  }
            //stlock
            
            //stack ops
            
            // conv
            // conv.i8
            | Code.Conv_I8, I32 :: stack ->
                let code =
                    code 
                    |> POP  RAX
                    |> MOVSXD   RAX EAX
                    |> PUSH RAX
                
                { state with
                    Code = code;
                    Stack = I64 :: stack;   }
            
            | Code.Conv_I8, OP :: stack when (OP = I64) || (OP = Native) ->
                { state with
                    Code = code;
                    Stack = I64 :: stack;   }
            
            | Code.Conv_I8, Float :: stack ->
                not_implemented ("conv.i8 float") |> out
                
                { state with
                    Code = code;
                    Stack = I64 :: stack;   }
            
            | Code.Conv_I8, item :: stack ->
                invalid_code ("conv.i8 " + item.ToString()) |> out
                
                { state with
                    Code = code;
                    Stack = I64 :: stack;  }
            // conv.i8
            
            // conv
            
            // comprasion
            // ceq
            | Code.Ceq, I32 :: I32 :: stack ->
                let code =
                    code
                    |> POP  RAX
                    |> POP  RBX
                    |> XOR  EAX EBX
                    |> JZ   (local "equal")
                    |> PUSH  ~*0
                    |> JMP  (local "exit")
                    |> LABEL    (local "equal")
                    |> PUSH ~*1
                    |> LABEL    (local "exit")
                
                { state with
                    Code = code;
                    Stack = I32 :: stack;   }
            
            | Code.Ceq, A64 :: B64 :: stack when (A64 = B64) && ((A64 = I64) || (A64 = Native) || (A64 = Reference) || (A64 = Object)) ->
                let code =
                    code
                    |> POP  RAX
                    |> POP  RBX
                    |> XOR  RAX RBX
                    |> JZ   (local "equal")
                    |> PUSH ~*0
                    |> JMP  (local "exit")
                    |> LABEL    (local "equal")
                    |> PUSH ~*1
                    |> LABEL    (local "exit")
                
                { state with
                    Code = code;
                    Stack = I32 :: stack;   }
            
            | Code.Ceq, I32 :: Native :: stack | Code.Ceq, Native :: I32 :: stack ->
                not_implemented "equality comprasion of i32 and native" |> out
                
                { state with
                    Code = code;
                    Stack = I32 :: stack;   }
            
            | Code.Ceq, _ ->
                invalid_code "invalid ceq operands" |> out
                
                invalid_program()
            // ceq
            
            // comprasion
            
            // branches
            // call
            | Code.Call, stack ->
                let target =
                    match instruction.Operand with
                    | :? MethodDefinition ->
                        instruction.Operand :?> MethodDefinition
                    | _ ->
                        not_implemented "call: method references" |> out
                        func
                
                let funcName = "/" + target.DeclaringType.FullName + "/" + target.Name + "/"
                // not_implemented "functions"
                let func = target
                let code =
                    match func.ReturnType.ReturnType.FullName with
                    | "System.Void" ->
                        code
                        |> CALL ~&funcName
                    | "System.UInt64" | "System.Int64" | "System.Int32" | "System.UInt32" | "System.Int16" | "System.UInt16"
                    | "System.Byte" | "System.SByte" | "System.IntPtr" | "System.UIntPtr" ->
                        code
                        |> CALL ~&funcName
                        |> PUSH RAX
                    | _ ->
                        let typedef = func.ReturnType.ReturnType |> resolver.ResolveType
                        if layout.[typedef].Size <= 8u then
                            code
                            |> CALL ~&funcName
                            |> PUSH RAX
                        else
                            code
                            |> CALL ~&funcName
                
                let stack = if target.IsStatic then stack else stack.Tail
                let stack =
                    Seq.skip (target.Parameters.Count) stack
                    |> List.of_seq
                let stack = if target.ReturnType.Equals(void_type) then stack else (layout.GetStackType (target.ReturnType.ReturnType |> resolver.ResolveType)) :: stack
                
                { state with
                    Code = code;
                    Stack = stack;  }
            // call
            
            // ret
            | Code.Ret, item :: stack->
                let code =
                    match func.ReturnType.ReturnType.FullName with
                    | "System.Void" ->
                        EPILOGUE code
                    | "System.UInt64" | "System.Int64" | "System.Int32" | "System.UInt32" | "System.Int16" | "System.UInt16"
                    | "System.Byte" | "System.SByte" | "System.IntPtr" | "System.UIntPtr" ->
                        code
                        |> POP  RAX
                        |> EPILOGUE
                    | _ ->
                        let typedef = func.ReturnType.ReturnType |> resolver.ResolveType
                        if layout.[typedef].Size <= 8u then
                            code
                            |> POP  RAX
                            |> EPILOGUE
                        else
                            // arguments
                            // old IP
                            // old RBP  <- RBP
                            // value
                            // not_implemented ("return value type " + func.ReturnType.ReturnType.FullName)
                            code
                            |> MOV  RAX (RBP + 0)                   // RAX = old RBP
                            |> MOV  RBX (RBP + 8)                   // RBX = old IP
                            |> MOV  RDI (RBP + -8 + int args_start)     // RDI = dest
                            |> MOV  RSI (RBP + -8)                  // RSI = source
                            |> MOV  RCX ~*((layout.[typedef].Size + 7u) / 8u)
                            |> STD
                            |> REP
                            |> MOVSQ
                            |> MOV  RBP RAX
                            |> JMP  RBX
                
                { state with
                    Code = code;
                    Stack = []; }
            // ret
            
            // br
            | Code.Br_S, [] ->
                let target = instruction.Operand :?> Instruction
                let target = name + target.Offset.ToString() + "/"
                let code =
                    code
                    |> JMP ~&target
                
                { state with
                    Code = code;
                    Stack = []; }
            
            | Code.Br, [] ->
                let target = instruction.Operand :?> Instruction
                let target = name + target.Offset.ToString() + "/"
                let code =
                    code
                    |> JMP ~&target
                    
                { state with
                    Code = code;
                    Stack = []; }
            
            | Code.Br, _ ->
                invalid_code "stack must be empty on branch" |> out
                
                invalid_program()
            // br
            
            // bne.un
            | Code.Bne_Un_S, I32 :: I32 :: [] ->
                let target = instruction.Operand :?> Instruction
                let offset = instruction.Offset
                let code =
                    code
                    |> POP  RAX
                    |> POP  RBX
                    |> XOR  EAX EBX
                    |> JZ   (local "equal")
                    |> JMP  ~&(name + offset.ToString() + "/")
                    |> LABEL    (local "equal")
                
                { state with
                    Code = code;
                    Stack = []; }
                
            | Code.Bne_Un_S, I64 :: I64 :: [] | Code.Bne_Un, Native :: Native :: [] | Code.Bne_Un, Native :: Reference :: [] 
                | Code.Bne_Un, Reference :: Native :: [] | Code.Bne_Un, Object :: Object :: [] ->
                let target = instruction.Operand :?> Instruction
                let offset = target.Offset
                let code =
                    code
                    |> POP  RAX
                    |> POP  RBX
                    |> XOR  RAX RBX
                    |> JZ   (local "equal")
                    |> JMP  ~&(name + offset.ToString() + "/")
                    |> LABEL    (local "equal")
                
                { state with
                    Code = code;
                    Stack = []; }
            
            | Code.Bne_Un, I32 :: I32 :: [] ->
                let target = instruction.Operand :?> Instruction
                let offset = target.Offset
                let code =
                    code
                    |> POP  RAX
                    |> POP  RBX
                    |> XOR  EAX EBX
                    |> JZ   (local "equal")
                    |> JMP  ~&(name + offset.ToString() + "/")
                    |> LABEL    (local "equal")
                
                { state with
                    Code = code;
                    Stack = []; }
            // bne.un
            
            // brtrue
            | Code.Brtrue, T64 :: [] when (T64 = I64) || (T64 = Native) || (I64 = Object) ->
                let target = instruction.Operand :?> Instruction
                let target = name + target.Offset.ToString() + "/"
                let code =
                    code
                    |> POP  RAX
                    |> AND  RAX RAX
                    |> JNZ  ~&target
                
                { state with
                    Code = code;
                    Stack = []; }
            
            | Code.Brtrue, T64 :: [] when (T64 = I64) || (T64 = Native) || (I64 = Object) ->
                let target = instruction.Operand :?> Instruction
                let target = name + target.Offset.ToString() + "/"
                let code =
                    code
                    |> POP  RAX
                    |> AND  RAX RAX
                    |> JNZ  ~&target
                
                { state with
                    Code = code;
                    Stack = []; }
            
            | Code.Brtrue, I32 :: [] ->
                let target = instruction.Operand :?> Instruction
                let target = name + target.Offset.ToString() + "/"
                let code =
                    code
                    |> POP  RAX
                    |> AND  EAX EAX
                    |> JNZ  ~&target
                
                { state with
                    Code = code;
                    Stack = []; }
            
            | Code.Brtrue_S, I32 :: [] ->
                let target = instruction.Operand :?> Instruction
                let target = name + target.Offset.ToString() + "/"
                let code =
                    code
                    |> POP  RAX
                    |> AND  EAX EAX
                    |> JNZ  ~&target
                
                { state with
                    Code = code;
                    Stack = []; }
            // brtrue
            
            // branches
            
            // structures
            | Code.Ldfld, Object :: stack ->
                let fieldref = instruction.Operand :?> FieldReference
                let objtype = resolver.ResolveType fieldref.DeclaringType
                let offset = layout.[objtype].Layout.[fieldref.Name]
                let fieldtype = resolver.ResolveType fieldref.FieldType
                let size = layout.[fieldtype].Size
                
                let code =
                    code
                    |> LEA  RDI (RSP + int (layout.GetStackSize objtype) + -8)
                    |> LEA  RSI (RSP + int offset + int (layout.GetStackSize fieldtype) + -8)
                    |> MOV  ECX ~*(int size)
                    |> REP
                    |> MOVSB
                    |> ADD  RSP ~*(int (layout.GetStackSize objtype - layout.GetStackSize fieldtype))
               
                let stackType =
                    match fieldtype.FullName with
                    | "System.Int32" | "System.UInt32" ->
                        I32
                    | "System.Int64" | "System.UInt64" ->
                        I64
                    | _ ->
                        Object
                
                { state with
                    Code = code;
                    Stack = stackType :: stack;    }
            // structures
        
        let code = PROLOGUE []
        let state = il |> List.fold_left compile { Stack = []; Code = code; }
        let code =
            if state.Stack = [] then state.Code
            else
                output {    Entry = invalid_code "non-empty stack on exit";
                            Position = null, state.Stack;   }
                
                invalid_program()
        
        COMMIT code
        |> Optimize.RemoveUnusedSymbols (Set.singleton name)
